#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "atomic.h"

#define WORK_SIZE 1000000
#define WORKER_COUNT 10

static pthread_t tmanager_id;
static pthread_t tworker_id[WORKER_COUNT];
static Atomic_t at_sum = {0};
static Atomic_t at_sum2 = {0};
static int sum = 0;
static int sum_with_mutex = 0;
static Mutex * mutex;
static Mutex * mutex2;
static SpinLock slk;

void * thread_worker(void *arg)
{
   printf("worker thread 0x%08x startup\n", (unsigned int)pthread_self());
   for (int i=0; i<WORK_SIZE; i++) {
#if defined(USE_ATOMICS)
       atomic_add(&at_sum, 1);
#endif
#if defined(USE_ATOMIC_SET)
       // it's wrong!
       // atomic_set(&at_sum2, at_sum2.value + 1);
       //
       // it's wrong, too!
       // atomic_set(&at_sum2, atomic_get(&at_sum2) + 1);
       //
       // it's wrong!
       // Atomic_t temp = {
       //     atomic_get(&at_sum2),
       // };
       // atomic_add(&temp, 1);
       // atomic_set(&at_sum2, atomic_get(&temp));

       // it's ok, but ugly.
       // mutex_lock(mutex2);
       // Atomic_t temp = {
       //     atomic_get(&at_sum2),
       // };
       // atomic_add(&temp, 1);
       // atomic_set(&at_sum2, atomic_get(&temp));
       // mutex_unlock(mutex2);

       // it's also ok, but ugly.
       mutex_lock(mutex2);
       atomic_set(&at_sum2, at_sum2.value + 1);
       mutex_unlock(mutex2);

#endif
#if defined(USE_DEFAULT)
       sum++;
#endif
#if defined(USE_MUTEX)
       mutex_lock(mutex);
       sum_with_mutex++;
       mutex_unlock(mutex);
#endif
#if defined(TEST_SPIN_LOCK_HUNG)
       spin_lock(&slk);
       printf("It will only be executed once because spinlock is locked!\n"
               "`top -H` to show cpu load\n"
               "`ctrl-c` to exit.\n"); 
#endif
#if defined(TEST_MUTEX_WITHOUT_UNLOCK)
       mutex_lock(mutex);
       printf("mutex locked and will not be unlocked,"
               "all thread will be blocked!\n");
       sum_with_mutex++;
       // mutex_unlock(mutex);
#endif
   }
   return NULL;
}

void * thread_management(void *arg)
{
   printf("management thread %08x startup\n", (unsigned int)pthread_self());
   for (int i=0;i<WORKER_COUNT;i++) {
       pthread_join(tworker_id[i], NULL);
   }
   printf("all worker threads finished.\n");
   return NULL;
}

int main(int argc, const char* argv[])
{
   mutex = mutex_create();
   mutex2 = mutex_create();
   for (int i=0;i<WORKER_COUNT;i++) {
       pthread_create(&tworker_id[i], NULL, thread_worker, NULL);
   }
   printf("created %d worker threads\n", WORKER_COUNT);

   pthread_create(&tmanager_id, NULL, thread_management, NULL);
   pthread_join(tmanager_id, NULL);
   mutex_destroy(mutex);
   mutex_destroy(mutex2);
#if defined(USE_ATOMICS)
   printf("the atomic_t sum: %d\n", at_sum.value);
#endif
#if defined(USE_ATOMIC_SET)
   printf("the atomic_t sum2 (use atomic_set): %d\n", at_sum2.value);
#endif
#if defined(USE_DEFAULT)
   printf("the int sum: %d\n", sum);
#endif
#if defined(USE_MUTEX)
   printf("the int sum (use mutex): %d\n", sum_with_mutex);
#endif
   return 0;
}
